<?php
/**
 *	KLF Framework
 *
 *	Written by Kristian Oye <kristianoye@gmail.com> on Oct 29, 2011
 *	Copyright (C) 2010-2011
 */
namespace KLF\System
{
	use KLF\System\Errors\KArgumentException;

	use \KLF\System\Collections\KList;

	/**
	 *  Class KMetadataReader
	 *
	 *  Reads attributes from comment metadata.
	 *
	 *  @package KLF
	 *  @access public
	 *  @author Kristian Oye <kristianoye@gmail.com>
	 *  @since 0.1
	 *  
	 *  @method KMetadataReader NewInstance
	 *  
	 *  @property string $ClassName
	 *  @property string $Namespace
	 *  
	 *  @property array $ClassData 
	 *  @property array $MethodData
	 *  @property array $PropertyData
	 *  
	 */
	class KMetadataReader extends KObject
	{
		/**
		 * The class that is being read.
		 * 
		 * @var string Class name that is being separated.
		 */
		protected $_rpropClassName;
		
		/**
		 * Returns the namespace of the class.
		 * 
		 * @var string $_rpropNamespace
		 */
		protected $_rpropNamespace;
		
		/**
		 * Class metadata.
		 * 
		 * @var array $_rpropClassData
		 */
		protected $_rpropClassData;
		
		/**
		 * Method metadata.
		 * 
		 * @var array $_rpropMethods
		 */
		protected $_rpropMethodData;
		
		/**
		 * Parent KMetadataReader object (if one exists).
		 * 
		 * @var KMetadataReader $_rpropParent
		 */
		protected $_rpropParent;
		
		/**
		 * Property metadata.
		 * 
		 * @var array $_rpropProperties
		 */
		protected $_rpropPropertyData;
		
		/**
		 * Contains a list of all cached reflection classes.
		 * 
		 * @var array $_rpropReflectionCache
		 */
		protected static $_rpropReflectionCache;
		
		/**
		 * The reflected class.
		 * 
		 * @var \ReflectionClass $_rpropReflection
		 */
		protected $_rpropReflection;
		
		/**
		 * Fetch a metadata object for the specified class.
		 * 
		 * @param string|object $classParam
		 * @return KMetadataReader
		 */
		public static function GetMetadata($classParam)
		{
			$className = is_object($classParam) ? get_class($classParam) : $classParam;
			
			if (!isset(self::$_rpropReflectionCache))
			{
				self::$_rpropReflectionCache = array();
			}
			if (!isset(self::$_rpropReflectionCache[$className]))
			{
				self::$_rpropReflectionCache[$className] = new KMetadataReader($className);
			}
			return self::$_rpropReflectionCache[$className];
		}
		
		/**
		 * Parse the metadata from a chunk of data.
		 *
		 * @author Kristian Oye <kristianoye@gmail.com>
		 * @since 0.1
		 * @return array An array of metadata
		 * @param $chunk A chunk of metadata.
		*/
		private static function ParseMetadata(\Reflector $chunk)
		{
			$block = $chunk->getDocComment();
			$matches = array();
			
			if (preg_match_all('/\[\[(?<att>[^\]]+)\]\]/', $block, $matches) !== false)
			{
				return $matches['att'];
			}
			return array();
		}
		
		/**
		 * Returns a ReflectionClass for the specified class name.
		 * 
		 * @param \ReflectionClass $reflection
		 * @return \ReflectionClass
		 */
		private static function ReflectionCache(\ReflectionClass $reflection)
		{
			$className = $reflection->getName();
			
			if (!isset(self::$_rpropReflectionCache))
			{
				self::$_rpropReflectionCache = array();
			}
			if (!isset(self::$_rpropReflectionCache[$className]))
			{
				self::$_rpropReflectionCache[$className] = null;
				
				if (($parent = $reflection->getParentClass()))
				{
					self::$_rpropReflectionCache[$className] = array_merge(
						self::$_rpropReflectionCache[$className],
						self::ReflectionCache($parent));
				}
			}
			return self::$_rpropReflectionCache[$className];
		}
		
		/**
		 * Constructs a new KMetadataReader
		 *
		 * @author Kristian Oye <kristianoye@gmail.com>
		 * @since 0.1
		*/
		public function __construct($c)
		{
			$className = (is_object($c) ? get_class($c) : $c);
			
			if (!isset(self::$_rpropReflectionCache[$className]))
			{
				$reflection = $this->_rpropReflection = new \ReflectionClass($className);
				
				$this->_rpropNamespace = $reflection->getNamespaceName();
				$this->_rpropClassName = $reflection->getName();
				
				if ($parent = $reflection->getParentClass())
				{
					$this->_rpropParent = self::GetMetadata($parent->getName());
				}
				self::$_rpropReflectionCache[$className] = $this;
			}
			else
			{
				throw new KArgumentException("Use KMetadataReader::GetMetadata() instead of constructing instances.");
			}
		}

		/**
		 * Get class level metadata.
		 * 
		 * @param string $value
		 * @param int $pc
		 */
		public function propClassData($value, $pc)
		{
			if (!isset($this->_rpropClassData))
			{
				$p = $this;
				$r = array();
				
				do
				{
					$data = self::ParseMetadata($p->_rpropReflection);
					
					if (isset($data) && count($data) > 0)
					{
						$r = array_merge($r, $data);
					}
					$p = $p->_rpropParent;
				}
				while($p);
				$this->_rpropClassData = $r;
			}
			return $this->_rpropClassData;
		}
		
		/**
		 * Get method metadata for a specific method.
		 * 
		 * @param string $methodName
		 * @param int $pc
		 * @return array
		 */
		public function GetMethodAttributes($methodName)
		{
			$result = $this->GetClassMethodAttributes($methodName);
			$meta = $this->_rpropParent;
			while($meta !== NULL)
			{
				if (count($data = $meta->GetClassMethodAttributes($methodName)))
				{
					$result = array_merge($result, $data);
				}
				$meta = $meta->_rpropParent;
			}
			return $result;
		}
		
		/**
		 * Initialize the method meta data if needed.
		 */
		public function GetClassMethodAttributes($methodName)
		{
			if (!isset($this->_rpropMethodData))
			{
				$this->_rpropMethodData = array();
			
				foreach($this->_rpropReflection->getMethods() as $m)
				{
					if ($m->getDeclaringClass()->getName() === $this->_rpropClassName)
					{
						$key = $m->getName();
						$data = self::ParseMetadata($m);
				
						if(count($data) == 0)
							continue;
				
						$this->_rpropMethodData[$key] = array(array($data, $this->_rpropClassName));
					}
				}
			}
			return (isset($this->_rpropMethodData[$methodName]) ? 
				$this->_rpropMethodData[$methodName] : array());
		}
		
		public function GetPropertyAttributes($propertyName)
		{
			$result = $this->GetClassPropertyAttributes($propertyName);
			$meta = $this->_rpropParent;
			while($meta !== NULL)
			{
				if (count($data = $meta->GetClassPropertyAttributes($propertyName)))
				{
					$result = array_merge($result, $data);
				}
				$meta = $meta->_rpropParent;
			}
			return $result;
		}
		
		public function GetClassPropertyAttributes($propertyName)
		{
			if (!isset($this->_rpropPropertyData))
			{
				$this->_rpropPropertyData = array();
					
				foreach($this->_rpropReflection->getProperties() as $p)
				{
					if ($p->getDeclaringClass()->getName() === $this->_rpropClassName)
					{
						$key = $p->getName();
						$data = self::ParseMetadata($p);
			
						if(count($data) == 0)
							continue;
			
						$this->_rpropPropertyData[$key] = array(array($data, $this->_rpropClassName));
					}
				}
			}
			return (isset($this->_rpropPropertyData[$propertyName]) ?
				$this->_rpropPropertyData[$propertyName] : array());
		}
		
		public function __toString()
		{
			return get_class() . "(" . $this->ClassName . ")";
		}
	}
}
